/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"
#include "BTreeIndex.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
  RecordFile rf;   // RecordFile containing the table
  RecordId   rid;  // record cursor for table scanning
  BTreeIndex btIndex; // B+ Tree Index

  RC     rc;
  int    key;
  string value;
  int    count;
  int    diff;
  bool useIndex; // Boolean value to determine whether the B+ Tree can be used
                 // for the SELECT query or not
  SelCond indexCond; // Determines which condition to use
  int startKey, finishKey; // If SELECT condition requires scanning (as in LEQ
                           // or GEQ), which keys to start/stop at.
  bool inclStart, inclFinish; // Whether to include the start/finish keys
  IndexCursor cursor; // The location of the key in the index

  // Initialize variables
  rid.pid = rid.sid = 0;
  count = 0;

  // open the table file
  if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
    fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
    return rc;
  }

  for (unsigned j = 0; j < cond.size(); j++)
  {
    // If the condition is on a key
    if (cond[j].attr == 1) {
      // If the condition is an equality condition 
      if (cond[j].comp == SelCond::EQ)
      {
        indexCond = cond[j];
        useIndex = true;
        break;
      } else if (cond[j].comp != SelCond::NE) {
        indexCond = cond[j];
        useIndex = true;
      }
    }
  }

  // if an index file is found, use the index to optimize the search
  if (btIndex.open(table + ".idx", 'r') == 0 && useIndex)
  {
    cout << "Index found!" << endl;

    // 
    switch (indexCond.comp) {
      case SelCond::EQ:
        startKey = finishKey = atoi(indexCond.value);
        inclStart = inclFinish = true;
        break;
      case SelCond::GT:
        startKey = atoi(indexCond.value);
        finishKey = INT_MAX;
        inclStart = false;
        inclFinish = true;
        break;
      case SelCond::LT:
        startKey = INT_MIN;
        finishKey = atoi(indexCond.value);
        inclStart = true;
        inclFinish = false;
        break;
      case SelCond::GE:
        startKey = atoi(indexCond.value);
        finishKey = INT_MAX;
        inclStart = true;
        inclFinish = true;
        break;
      case SelCond::LE:
        startKey = INT_MIN;
        finishKey = atoi(indexCond.value);
        inclStart = true;
        inclFinish = true;
        break;
    }

    // Find the start key
    if ((rc = btIndex.locate(startKey, cursor)) < 0) {
      fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
      goto exit_select;
    }

    if ((rc = btIndex.readForward(cursor, key, rid)) < 0) {
      fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
      goto exit_select;
    }

    while ((key < finishKey && !inclFinish) || (key <= finishKey && inclFinish))
    {
      // If we are on the first key, don't include it if unnecessary
      if (key == startKey && !inclStart)
        continue;

      if (attr == 2 || attr == 3)
      {
        if ((rc = rf.read(rid, key, value)) < 0) {
          fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
          goto exit_select;
        }
      }
      // check the conditions on the tuple
      for (unsigned i = 0; i < cond.size(); i++)
      {
        // compute the difference between the tuple value and the condition value
        switch (cond[i].attr) {
          case 1:
            diff = key - atoi(cond[i].value);
            break;
          case 2:
            diff = strcmp(value.c_str(), cond[i].value);
            break;
        }

        // skip the tuple if any condition is not met
        switch (cond[i].comp) {
          case SelCond::EQ:
            if (diff != 0) goto next_tuple1;
            break;
          case SelCond::NE:
            if (diff == 0) goto next_tuple1;
            break;
          case SelCond::GT:
            if (diff <= 0) goto next_tuple1;
            break;
          case SelCond::LT:
            if (diff >= 0) goto next_tuple1;
            break;
          case SelCond::GE:
            if (diff < 0) goto next_tuple1;
            break;
          case SelCond::LE:
            if (diff > 0) goto next_tuple1;
            break;
        }
      }

      // the condition is met for the tuple. 
      // increase matching tuple counter
      count++;

      // print the tuple 
      switch (attr) {
        case 1:  // SELECT key
          fprintf(stdout, "%d\n", key);
          break;
        case 2:  // SELECT value
          fprintf(stdout, "%s\n", value.c_str());
          break;
        case 3:  // SELECT *
          fprintf(stdout, "%d '%s'\n", key, value.c_str());
          break;
      }

      // move to the next tuple
      next_tuple1:
      btIndex.readForward(cursor, key, rid);
    }
  } else {
    //If there is no index, scan all records linearly

    // scan the table file from the beginning
    while (rid < rf.endRid()) {
      // read the tuple
      if ((rc = rf.read(rid, key, value)) < 0) {
        fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
        goto exit_select;
      }

      // check the conditions on the tuple
      for (unsigned i = 0; i < cond.size(); i++)
      {
        // compute the difference between the tuple value and the condition value
        switch (cond[i].attr) {
          case 1:
            diff = key - atoi(cond[i].value);
            break;
          case 2:
            diff = strcmp(value.c_str(), cond[i].value);
            break;
        }

        // skip the tuple if any condition is not met
        switch (cond[i].comp) {
          case SelCond::EQ:
            if (diff != 0) goto next_tuple2;
            break;
          case SelCond::NE:
            if (diff == 0) goto next_tuple2;
            break;
          case SelCond::GT:
            if (diff <= 0) goto next_tuple2;
            break;
          case SelCond::LT:
            if (diff >= 0) goto next_tuple2;
            break;
          case SelCond::GE:
            if (diff < 0) goto next_tuple2;
            break;
          case SelCond::LE:
            if (diff > 0) goto next_tuple2;
             break;
        }
      }

      // the condition is met for the tuple. 
      // increase matching tuple counter
      count++;

      // print the tuple 
      switch (attr) {
        case 1:  // SELECT key
          fprintf(stdout, "%d\n", key);
          break;
        case 2:  // SELECT value
          fprintf(stdout, "%s\n", value.c_str());
          break;
        case 3:  // SELECT *
          fprintf(stdout, "%d '%s'\n", key, value.c_str());
          break;
      }

      // move to the next tuple
      next_tuple2:
      ++rid;
    }
  }

  // print matching tuple count if "select count(*)"
  if (attr == 4) {
    fprintf(stdout, "%d\n", count);
  }
  rc = 0;

  // close the table file and return
  exit_select:
  rf.close();
  return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
  char *fileName = (char*)loadfile.c_str();
  ifstream loadStream (fileName);
  string line;
  int key;
  string value;
  RecordId rid;
  RecordFile records (table+".tbl", 'w');
  BTreeIndex btIndex;
  if (index)
  {
    string indexName = table + ".idx";
    btIndex.open((const string)indexName, 'w');
  }
  //records.open(table+".tbl", 'w');
  if (loadStream.is_open())
  {
    while ( loadStream.good() )
    {
      getline (loadStream,line);
      SqlEngine::parseLoadLine(line, key, value);
	  records.append(key, value, rid);
      if (index)
          btIndex.insert(key, (const RecordId)rid);
    }
    loadStream.close();
	records.close();
    if (index)
        btIndex.close();
  }
  return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}
